iT邦幫忙

2022 iThome 鐵人賽

DAY 23
0

文章同步發表至 Medium

根據前一篇確定好檔案的座標系統之後,我們就來正式匯入資料吧。

我們可以根據政府資料開放平臺上,景點的頁面中所提供的觀光資訊標準格式,把手上的 Shapefile 內容過濾出我們所需要的資訊,存入資料庫。

建立 Table

在建立之前,記得先把 PostGIS 引入,才能正確的使用 geometry 的型別喔:

-- 引入 PostGIS
CREATE EXTENSION postgis;

-- 建立 table,以景點為例
CREATE TABLE public.scenic_spot
(
    id character varying NOT NULL,                -- 代碼
    name character varying NOT NULL,              -- 名稱
    telephone character varying,                  -- 電話
    address character varying,                    -- 地址
    county character varying,                     -- 縣市
    town character varying,                       -- 鄉鎮市區
    x double precision NOT NULL,                  -- 主要出入口或主要建築物所在位置的經度
    y double precision NOT NULL,                  -- 主要出入口或主要建築物所在位置的緯度
    type integer,                                 -- 景點分類
    park character varying,                       -- 停車位資訊
    info character varying,                       -- 參觀資訊
    update_time timestamp without time zone,      -- 最後異動時間
    geom geometry NOT NULL,
    PRIMARY KEY (id)
);

讀取檔案

跟資料庫的溝通我選擇使用 EntityFrameworkCore 的方式,因此要進行的步驟總共有三個:

  1. 將資料庫的 Table 轉換成 Model
  2. 資料匯入

1. Table 轉換成 Model

打開 Terminal 的介面(Rider 快捷鍵: Ctrl+Alt+1 ),移動到 .csproj 的同一層資料夾底下,輸入指令:

dotnet ef dbcontext scaffold "Host=localhost;Database=tourism;Username=postgres;Password=0000;" Npgsql.EntityFrameworkCore.PostgreSQL -o ./Models/Db -c TourismEntity -f

相關的參數前幾篇有解釋過了,這裡就不再贅述。要注意的是,如果沒有特別指定 -f 的話,在執行的時候可能會出現錯誤:

The following file(s) already exist in directory 'C:\Users\user\Documents\GitHub\tourism-import\tourism-import\Models\Db': TourismEntity.cs,ScenicSpot
.cs. Use the Force flag to overwrite these files.

2. 資料匯入

using Microsoft.Extensions.Configuration;
using NetTopologySuite.IO.Esri;
using tourism_import;
using tourism_import.Models.Db;

var shapefilePath = @"shapefile path";
var scenicSpots = new List<ScenicSpot>();

var features = Shapefile.ReadAllFeatures(shapefilePath);
var scenicSpots = features.Select(f =>
{
    var attributes = f.Attributes.GetValues();
    return new ScenicSpot
    {
        Id = attributes[(int)EnumAttributeName.Id].ToString(),
        Name = attributes[(int)EnumAttributeName.Name].ToString(),
        Telephone = attributes[(int)EnumAttributeName.Tel].ToString(),
        Address = attributes[(int)EnumAttributeName.Add].ToString(),
        County = attributes[(int)EnumAttributeName.Region].ToString(),
        Town = attributes[(int)EnumAttributeName.Town].ToString(),
        X = attributes[(int)EnumAttributeName.Px].ToDouble(),
        Y = attributes[(int)EnumAttributeName.Py].ToDouble(),
        Type = attributes[(int)EnumAttributeName.Orgclass].ToString(),
        Park = attributes[(int)EnumAttributeName.Parkin_nfo].ToString(),
        Info = attributes[(int)EnumAttributeName.Ticketinfo].ToString(),
        UpdateTime = attributes[(int)EnumAttributeName.Changetime].ToDateTime(),
        Geom = f.Geometry,
    };
}).ToList();

var db = new TourismEntity();
await db.ScenicSpots.AddRangeAsync(scenicSpots);
await db.SaveChangesAsync();

// Shapefile 欄位對應位置
public enum EnumAttributeName
{
    Id = 0,            // 代碼
    Name = 1,          // 名稱
    Tel = 3,           // 電話
    Add = 4,           // 地址
    Region = 6,        // 縣市
    Town = 7,          // 鄉鎮市區
    Px = 16,           // 主要出入口或主要建築物所在位置的經度
    Py = 17,           // 主要出入口或主要建築物所在位置的緯度
    Orgclass = 18,     // 景點分類
    Parkin_nfo = 24,   // 停車位資訊
    Ticketinfo = 27,   // 參觀資訊
    Changetime = 29,   // 最後異動時間
}

因為利用 features.Attributes.GetValues() 所讀取出來的資料都是文字,所以我們需要在第 21 行和第 22 行使用自定義的 ToDouble() 和第 26 行的 ToDateTime(),轉成先前資料庫所定義的格式。

public static class Extensions
{
    public static double ToDouble(this object attr)
    {
        return double.Parse(attr.ToString());
    }
    
    public static DateTime? ToDateTime(this object attr)
    {
        if (string.IsNullOrEmpty(attr.ToString())) return null;
        var dateTimeString = (attr.ToString()).Split('+')[0];
        var dateString = dateTimeString.Split('T')[0];
        var timeString = dateTimeString.Split('T')[1];
        var year = int.Parse(dateString.Split('-')[0]);
        var month = int.Parse(dateString.Split('-')[1]);
        var day = int.Parse(dateString.Split('-')[2]);
        var hour = int.Parse(timeString.Split(':')[0]);
        var minute = int.Parse(timeString.Split(':')[1]);
        var second = int.Parse(timeString.Split(':')[2]);
        return new DateTime(year, month, day, hour, minute, second);
    }
}

執行完成之後可以利用 pgAdmin 檢視一下資料:


上一篇
旅遊規劃小專案 0 - 資料準備
下一篇
旅遊規劃小專案 2 - API
系列文
歡迎來到 GIS 的世界!30 天從後端開始學 GIS30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言